package SPAN::Types::Math::Calculus;

use warnings;
use strict;

use Moose::Util::TypeConstraints;
use Reaction::Types::Core;
use Data::Dump;

subtype 'Expression' => as 'NonEmptyStr' => where {
    my $exp = Math::Calculus::Expression->new;
    return unless $exp->setExpression($_);
    return 1;
} => message {'Not a valid Expression'};

subtype 'Variable' => as 'NonEmptySimpleStr' => where {/^[[:alpha:]]+$/} =>
    message {'Variables can only contain letters'};

subtype 'Real' => as 'Num' => where {1} => message {'Must be a Real number'};

subtype 'Vector' => as 'ArrayRef' => where {
    my $num = find_type_constraint('Num');
    $num->check($_) or return foreach @$_;
    return 1;
} => message {'Must be a Vector'};

subtype 'VectorStr' => as 'NonEmptyStr' => where {
    my $num = find_type_constraint('Num');
    my @num = split /\s*\,\s*/;
    $num->check($_) or return foreach @num;
    return 1;
};

coerce 'Vector' => from 'VectorStr' => via { [ split /\s*\,\s*/ ] };

subtype 'Matrix' => as 'ArrayRef' => where {
    my $vector = find_type_constraint('Vector');
    $vector->check($_) or return foreach @$_;
    return 1;
} => message {'Must be a Matrix'};

subtype 'MatrixStr' => as 'NonEmptyStr' => where {
    my $vector = find_type_constraint('VectorStr');

    while(s/^\s*\[(.+?)\]\s*//g) { #consume the string while checking
        return unless $vector->check($1);
    }
    return if $_ ne ''; # if we haven't consumed the entire string, it's wrong

    return 1;
};

coerce 'Matrix' => from 'MatrixStr' => via {
    my $vector = find_type_constraint('Vector');

    my @rows;

    # have to use a lexical variable so moose doesn't touch it
    # that way, this loop will end some day ;)
    my $val = $_;
    while($val =~ /\[(.+?)\]/g) {
        push @rows, $vector->coerce($1);
    }

    return \@rows;
};

1;
